package com.xjh.him.netty;

import com.xjh.him.netty.codec.LengthFieldDecoder;
import com.xjh.him.netty.codec.MsgDecoder;
import com.xjh.him.netty.codec.MsgEncoder;
import com.xjh.him.netty.handler.BaseHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.epoll.Epoll;
import io.netty.channel.epoll.EpollEventLoopGroup;
import io.netty.channel.epoll.EpollServerSocketChannel;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import lombok.extern.slf4j.Slf4j;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;

@Slf4j
public class NettyServer {
    private static boolean isUnixPlatform = false;
    private int port;
    private EventLoopGroup bossGroup;
    private EventLoopGroup workerGroup;
    private ServerBootstrap bootstrap;
    private List<ChannelInboundHandler> handlerList;
    private ChannelFuture future;

    static {
        if (System.getProperty("os.name").toLowerCase().contains("linux")) {
            isUnixPlatform = true;
        }
    }

    public static void main(String[] args) {
        NettyServer server = new NettyServer(8001, Arrays.asList(new BaseHandler()));
        server.start();
    }

    public NettyServer(int port, List<ChannelInboundHandler> handlerList) {
        this.port = port;
        this.handlerList = handlerList;

        if (useEpoll()) {
            bossGroup = new EpollEventLoopGroup(1, new ThreadFactory() {
                private AtomicInteger threadIndex = new AtomicInteger(0);

                @Override
                public Thread newThread(Runnable r) {
                    return new Thread(r, String.format("NettyEpollBoss_%d", threadIndex.getAndIncrement()));
                }
            });
            workerGroup = new EpollEventLoopGroup(3, new ThreadFactory() {
                private AtomicInteger threadIndex = new AtomicInteger(0);

                @Override
                public Thread newThread(Runnable r) {
                    return new Thread(r, String.format("NettyEpollWorker_%d", threadIndex.getAndIncrement()));
                }
            });
        }else {
            bossGroup = new NioEventLoopGroup(1, new ThreadFactory() {
                private AtomicInteger threadIndex = new AtomicInteger(0);

                @Override
                public Thread newThread(Runnable r) {
                    return new Thread(r, String.format("NettyNioBoss_%d", threadIndex.getAndIncrement()));
                }
            });
            workerGroup = new NioEventLoopGroup(3, new ThreadFactory() {
                private AtomicInteger threadIndex = new AtomicInteger(0);

                @Override
                public Thread newThread(Runnable r) {
                    return new Thread(r, String.format("NettyNioWorker_%d", threadIndex.getAndIncrement()));
                }
            });
        }
        bootstrap = new ServerBootstrap();
    }

    private boolean useEpoll() {
        return isUnixPlatform && Epoll.isAvailable();
    }

    public void start() {
        try {
            bootstrap.group(bossGroup, workerGroup)
                .channel(useEpoll() ?
                        EpollServerSocketChannel.class :
                        NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel channel) {
                        ChannelPipeline pipeline = channel.pipeline();

                        pipeline.addLast("Server LengthFieldDecoder", new LengthFieldDecoder());
                        pipeline.addLast("Server Decoder", new MsgDecoder());
                        if (handlerList != null) {
                            for (ChannelInboundHandler handler : handlerList) {
                                log.info("handler:{} register", handler.getClass().getName());
                                pipeline.addLast(handler);
                            }
                        }
                        pipeline.addLast("Server Encoder", new MsgEncoder());
                    }
                });
            bootstrap.bind(port).sync();
            log.info("start netty server thread, bind {}", port);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}
